[contents] [prev] [next] [top] [bottom] (5 out of 7)

Defining the new, init, and afterInit Methods

When you create a new instance of a class using the new method or object construct, several things happen that create and initialize the instance of that class. (The following sections on initialization apply equally to the new method and the object construct.) As shown in the figure, the new method (1) allocates memory for the new object and sets up other internal values; it then (2) calls init on the newly created instance to initialize it, passing along any arguments that had been specified to new; it finally (3) calls afterInit for post-initialization, again passing along any arguments that had been specified to new. Notice that new is a class method (operates on a class), while init and afterInit are instance methods.

While its is routine to call the new method, you should never directly call init or afterInit (which are automatically called by new); doing so will not re-initialize the object, and might put the instance into an unknown state or cause unexpected behavior. Calling init, in particular, might cause your program to crash. This section describes init and afterInit, and how to override them in your own classes to specialize the initialization.

When you create a subclass of an existing class, you may often want to override how it is initialized. To do this, you override the init and afterInit methods in your subclass. You are not required to define either an init or afterInit method in your subclass; you only need to define them if your subclass's initialization behavior is different from that of its superclasses.

The new method should almost never be overridden. (You could override new if you want to limit the number of instances that can be created.)

The init method initializes the new instance of its class. Every instantiable class has an init method, which contains implementation to set up some of the initial values of a new instance.

Be aware that the instance is in an incomplete state until init is done. This is very important to keep in mind. It is not safe to do anything that may have the side effect of calling a method on this instance, which is still in an incomplete state. Calling a method on an incomplete instance can cause your program to crash.

However, the afterInit method is a safe place to perform all post-initialization work. Since the object is fully initialized at this point, it's safe to call a method on it. The default afterInit method for most core classes is empty. The Collection classes have an afterInit method that allows you to add initial keys and values to that new collection. Where afterInit comes in handy, again, is in subclasses you define.

If you create a subclass of Window, you might define afterInit to create whatever objects the window is supposed to initially requite or contain, such as pushbuttons, bitmaps, players, and other objects. Then every time new is called on that class, the new window will automatically get the objects it needs.

Both init and afterInit are defined in similar ways and are described in the following sections.

Keyword Arguments

Keyword arguments are arguments in a method or function that have explicit keywords. You can recognize a keyword argument by the colon (:) used to separate the keyword from the value:

keyword:value

The new method for many classes uses keyword arguments. For example, the following code creates a new instance of a Point class; it uses the keywords x and y to intialize the point to coordinates 10,20:

new Point x:10 y:20

Keyword arguments are described in the following section "Defining init Methods."

Defining init Methods

The definition of the init method has the syntax shown below. It is defined to take only one positional argument, self, one rest argument restArg (which is actually a collection of arguments), and any number of keyword arguments keywordArgs. Keyword arguments are not restricted to only the init method; any method definition can contain keyword arguments.

method init self #rest restArg [ #key keywordArgs ] -> (
. . .
apply nextMethod self keywordArgs restArg
. . .
)
where:

As you recall, the new method automatically calls init. When you call any function or method with a series of keyword arguments, these arguments are wrapped in a collection. In this case, the collection of keyword arguments are passed to restArg in init.

Use #key to define any new keyword arguments. An example of a keyword argument is scale:(10). Notice that the parentheses are required around the default value, even if the value is simply a number and not a complex expression. For a complete description of keyword arguments, see the discussion that begins on page 96 of Chapter 5, "Functions, Threads and Pipes."

In general, the new method that calls init can contain a variable number of keyword arguments, and should therefore include #rest restArg, as shown, which collects those keyword arguments together into the collection restArg. (It's interesting to note that #rest can collect any kind of arguments. It is used to collect keyword arguments in the init method, whereas it can be used to collect non-keyword arguments in functions.)

If the new method that calls init does not contain a variable number of keyword arguments, then you do not need to include #rest restArg. However, about the only time you can be sure of this constraint is with a non-subclassable (sealed) class that inherits directly from RootObject.

Unless you really know what you're doing, there are only two things you can do inside the body of init:

It's important to call nextMethod, because init is implemented in RootObject; nextMethod ensures that that implementation is called. Notice that it does not matter what init returns; its return value is ignored.

Here is a "minimal" implementation of the init method which does no specialization:

method init self #rest args -> (
	apply nextMethod self args
)

In this example, any keywords supplied to the new method are passed into init as the collection args; this collection is then passed up to the init method of its superclasses using apply nextMethod.

Initializing Keywords and Instance Variables

Notice you have three places you can set initial values in the init definition:

Examples of Overriding init

Here are some examples of class definitions that override init:

The BlahShape class specializes Rect to add an extra instance variable, which is initialized to a linked list.

class BlahShape (Rect)
	inst vars
		blahList
	inst methods
		method init self #rest args -> (
			apply nextMethod self args
			self.blahList := new LinkedList
			return self
		)
end

The TextTable class, suitable for creating a table, specializes ArrayList by adding three instance variables. It specializes the init method to set values for those instance variables, supplying a default of 10 columns and a name of "Laura's Table". It also supplies a default value for initialSize (one of the keyword arguments that ArrayList defines) to set a TextTable instance to 30 elements.

class TextTable (ArrayList) 
inst vars
		recClass, columns, name
inst methods
method init self #rest args \
#key columns:(10) tabClass: name:("Laura's Table") -> (
			apply nextMethod self initialSize:(30) args
			self.recClass := tabClass
			self.columns := columns
			self.name := name
			return self
	)
end

ArrowPresenter is a subclass of TwoDPresenter that adds two instance variables and overrides by setting values for those instance variables. Its init method also supplies a different default value for one of the keyword arguments that the TwoDPresenter class uses.

class ArrowPresenter (TwoDPresenter)
	instance variables strokePaint, direction
	instance methods
		method init self #rest args ->(
			apply nextMethod self boundary:(new Rect x2:15 y2:15) args
			self.strokePaint := blackBrush
			self.direction := @up
			return self
		)
end 

Passing Initial Values in init to Superclasses

If you calculate or otherwise determine values for keyword arguments that need to be supplied to the init methods defined by superclasses, you can pass those values in keyword-value pairs using nextMethod.

For example, if your superclass's init method defines a scale keyword argument, but you want instances of your class to always have a scale of 15, you could supply the value 15 to the scale keyword in the nextMethod call.

-- Initial value of scale is always 15, even if passed in another value
method init self #rest args -> (
	apply nextMethod self scale:15 args
	return self
)

Now, even if an alternate value for scale is supplied when this method is called, a scale of 15 is always used. Both values appear in the nextMethod arguments (one explicitly as scale, the other is in args), but nextMethod always chooses the first value of the key it finds.

In addition, a subtle point with the init method (or with any function or method that defines both rest and keyword arguments) is that the arg argument collects only keyword arguments that have been supplied directly in the call to init; it does not collect keyword arguments for which you've simply specified default values in the init definition.

For example, say that your superclass takes an optional scale keyword. Therefore, if you want to be able to override the default value, you might assume that simply specifying a default value to the keyword argumenta after #key will work:

-- Incorrect implementation
-- This method does not pass the default scale value to its superclasses
method init self #rest args #key scale:(15) -> (
	apply nextMethod self args
)

The problem in this example is that unless you specifically give a value for scale when you create an instance of your class, the value of scale does not get appended to args, and so does not automatically get passed to your superclass's init method.

To pass the default value of any keyword argument you define in your own init method to your superclass's init, you must explicitly pass that keyword along in the nextMethod call, and supply its current value:

-- Correct implementation
-- This method passes the default value of scale to its superclasses
method init self #rest args #key scale:(15) -> (
	apply nextMethod self scale:scale args
)

This allows you to specify the default value of 15 to the scale keyword argument. It also allows the value of scale to be overridden by a value supplied with new or object.

Defining afterInit Methods

The afterInit method is identical in structure to the init method. Just as with init, the afterInit method requires a rest argument, and you must call apply nextMethod at some point in the body of afterInit so that all superclasses have a chance to add their post-initialization behavior.

However, it is much less restrictive than init in what you can implement inside the body, since self at this point is fully initialized.

method afterInit self #rest restArg [ #key keywordArgs ] -> (
. . .
apply nextMethod self keywordArgs restArg
. . .
)
See the previous init method definition for descriptions of restArg, keywordArgs, and the other elements of this definition.

The following is a simple example using init and afterInit. The class SameKey inherits from SortedKeyedArray, but for every key-value pair in the array, each key is exactly the same object. Only the values vary. The SameKey class has an instance variable called, appropriately, key, which holds the key that is repeated for every key-value pair. When you call new on this class, you have two available keyword arguments: key, which specifies the value for the key instance variable, and vals, which holds an array of potential values for the new collection. During initialization of the new object, the value of key is assigned to the key instance variable, and the contents of vals are added to the collection.

class SameKey (SortedKeyedArray)
	instance vars 
		key
	instance methods
		method init self #rest args #key key: -> (
			apply nextMethod self args
			self.key := key
		)
		method afterInit self #rest args #key vals: -> (
			apply nextMethod self args
			for i in vals do add self self.key i 
		)
end

The init method defines a keyword argument, key, to hold that key, and assigns that value to the instance variable self.key. Because adding a new element to a collection requires a method call on that object, an afterInit method is required. The afterInit method defines an additional keyword argument, vals, which holds an array of possible values for the new class. The afterInit method then adds the new key-value pair (using the key specified in the key instance variable) to the new collection.

When a new instance of SameKey is created, it looks like this:

newSameKey := new SameKey key:@flavor \
vals:#(@cherry,@watermelon,@peach)
#(@flavor:@peach, @flavor:@watermelon, @flavor:@cherry) as SameKey 


This document is part of the ScriptX Language Guide, one of the volumes of the ScriptX Technical Reference Series. ScriptX is developed by the ScriptX Engineering Team at Apple Computer, successor to the Kaleida Engineering Team at Kaleida Labs, Inc.

Copyright 1996 Apple Computer, Inc. All Rights Reserved.